package library.http;

import android.os.Build;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;

import okhttp3.Call;

class InvokeHandler implements InvocationHandler {

    /**
     * method caches
     */
    private final Map<Method, MethodCache> mMethodCache = new LinkedHashMap<>();

    /**
     * a base task ,will be reused as common config;
     */
    private final HttpTaskProxy httpTask;

    public InvokeHandler(Class<?> cls, HttpAdapter adapter) {
        this.httpTask = new HttpTaskProxy(cls, adapter);
    }

    @Override
    public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //若有实例，则直接执行
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            if (method.isDefault()) {
                throw new UnsupportedOperationException("请不要使用java8 default method();");
            }
        }

        Class returnClass = method.getReturnType();
        Type returnType = method.getGenericReturnType();
        if (!Hook.class.isAssignableFrom(returnClass)
                && !Call.class.isAssignableFrom(returnClass)) {
            HttpUtils.error(method, "返回值类型限定\n" +
                            "支持 1: [%s] 类型 ;\n" +
                            "支持 2: [%s] 类型或其子类 ;\n" +
                            "发现: [%s] ",
                    Hook.class.getName(), Call.class.getName(), returnClass.getName());
        }
        try {
            //否则使用代理
            MethodCache methodCache = loadMethod(method);
            HttpTask task = methodCache.parse(args);
            if (Hook.class.isAssignableFrom(returnClass)) {
                if (returnType instanceof ParameterizedType) {
                    final ParameterizedType type = (ParameterizedType) returnType;
                    final Type actualType = type.getActualTypeArguments()[0];
                    final Type rType = actualType;
                    final Class rClass = actualType instanceof Class ? (Class) actualType : (Class) ((ParameterizedType) returnType).getRawType();

                    task.returnClass(rClass);
                    task.returnType(rType);
                }
                return newInstance(returnClass, new Class[]{HttpTask.class}, new Object[]{task});
            } else if (Call.class.isAssignableFrom(returnClass)) {
                return task.call();
            }
        } catch (Exception e) {
            HttpUtils.error(method, e);
        }
        return null;
    }

    private synchronized MethodCache loadMethod(Method method) throws Exception {
        MethodCache cache = mMethodCache.get(method);
        if (cache == null) {
            synchronized (InvokeHandler.class) {
                if (cache == null) {
                    //reuse common config
                    cache = new MethodCache(method, httpTask.newInstance());
                    mMethodCache.put(method, cache);
                }
            }
        }
        return cache;
    }

    private <T> T newInstance(Class<T> clz, Class[] argTypes, Object[] argValues) throws Exception {
        Constructor constructor = clz.getDeclaredConstructor(argTypes);
        constructor.setAccessible(true);
        T instance = (T) constructor.newInstance(argValues);
        return instance;
    }
}
